using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace pal.SvnConf.Core
{
    //Understands the reading from and writing to an SVN authz file
    public class AuthzFile : IConfigurationFile
    {
        private const char SECTION_NAME_PREFIX = '[';
        private const char SECTION_NAME_SUFFIX = ']';
        private const char SPACE = ' ';

        private readonly TextReader _textReader;
        private readonly PasswdFile _passwdFile;
        private List<Group> _groups = new List<Group>();
        private List<Acl> _acls = new List<Acl>();
        private string _groupsSection;
        private List<string> _aclSections;
        private const string GROUPS_SECTION_NAME = "groups";
        private string GROUPS_SECTION_TOKEN = SECTION_NAME_PREFIX + GROUPS_SECTION_NAME + SECTION_NAME_SUFFIX;
        private string _fileContents;
        private bool _parsingGroupsSection = false;
        private bool _parsingAclSection = false;

        public AuthzFile(TextReader textReader, PasswdFile passwdFile)
        {
            _textReader = textReader;
            _passwdFile = passwdFile;
        }

        public List<Group> Groups
        {
            get { return _groups; }
        }

        public List<Acl> Acls
        {
            get { return _acls; }
        }

        public void Parse()
        {
            _fileContents = _textReader.ReadToEnd();
            _textReader.Close();

            _groupsSection = extractGroupsSection();
            parseGroups();

            _aclSections = extractAclSections();
            parseAcls();
        }

        public void Persist(TextWriter textWriter)
        {
            startGroupsSection(textWriter);
            foreach (Group group in _groups)
                writeGroup(textWriter, group);

            writeBlankLine(textWriter);

            foreach (Acl acl in _acls)
                writeAcl(textWriter, acl);
        }

        private void writeAcl(TextWriter textWriter, Acl acl)
        {
            startAclSection(textWriter, acl.Name);
            foreach (AuthorizationEntry authorizationEntry in acl.AuthorizationEntries)
            {
                textWriter.WriteLine("{0} = {1}", authorizationEntry.SecurityPrincipalName,
                                     authorizationEntry.Authorization);
            }
        }

        private void startAclSection(TextWriter textWriter, string name)
        {
            textWriter.WriteLine(SECTION_NAME_PREFIX + name + SECTION_NAME_SUFFIX);
        }

        private void writeBlankLine(TextWriter textWriter)
        {
            textWriter.WriteLine();
        }

        private void writeGroup(TextWriter textWriter, Group group)
        {
            string[] memberNames = new string[group.Members.Count];
            for (int i = 0; i < group.Members.Count; i++)
                memberNames[i] = group.Members[i].ConfigurationUsageName;

            textWriter.WriteLine("{0} = {1}", group.Name, string.Join(",", memberNames));
        }

        private void startGroupsSection(TextWriter textWriter)
        {
            textWriter.WriteLine(GROUPS_SECTION_TOKEN);
        }

        private void parseAcls()
        {
            foreach (string aclSection in _aclSections)
            {
                _acls.Add(Acl.Parse(aclSection));
            }
        }

        private void parseGroups()
        {
            StringReader reader = new StringReader(_groupsSection);
            do
            {
                string line = reader.ReadLine();

                if (string.IsNullOrEmpty(line.Trim()))
                    continue;

                if (isGroupsSectionStart(line))
                {
                    _parsingGroupsSection = true;
                    continue;
                }

                if (_parsingGroupsSection)
                    _groups.Add(Group.Parse(line, _passwdFile, this));

                if (isGroupsSectionEnd(line))
                {
                    _parsingGroupsSection = false;
                    break;
                }
            } while (streamHasMoreEntries(reader));
        }

        private bool streamHasMoreEntries(TextReader textReader)
        {
            return textReader.Peek() != -1;
        }

        private bool isGroupsSectionEnd(string line)
        {
            return isSectionStart(line) && !isGroupSection(line);
        }

        private bool isGroupsSectionStart(string line)
        {
            return isSectionStart(line) && isGroupSection(line);
        }

        private bool isGroupSection(string line)
        {
            return sectionName(line).Equals(GROUPS_SECTION_NAME);
        }

        private string sectionName(string line)
        {
            return line.Trim(SECTION_NAME_PREFIX, SECTION_NAME_SUFFIX, SPACE);
        }

        private bool isSectionStart(string line)
        {
            return line.StartsWith(SECTION_NAME_PREFIX.ToString());
        }

        private List<string> extractAclSections()
        {
            List<string> aclSections = new List<string>();
            StringBuilder currentSection = null;
            StringReader reader = new StringReader(_fileContents);
            do
            {
                string line = reader.ReadLine();
                if (isAclSection(line))
                {
                    currentSection = new StringBuilder();
                    currentSection.AppendLine(line);
                    _parsingAclSection = true;
                    continue;
                }

                if (_parsingAclSection)
                {
                    if (currentSection != null) currentSection.AppendLine(line);
                }

                if (endOfCurrentSection(reader))
                {
                    if (currentSection != null) aclSections.Add(currentSection.ToString());
                    _parsingAclSection = false;
                }
            } while (streamHasMoreEntries(reader));
            return aclSections;
        }

        private bool endOfCurrentSection(TextReader reader)
        {
            return !streamHasMoreEntries(reader) || reader.Peek() == SECTION_NAME_PREFIX;
        }

        private bool isAclSection(string line)
        {
            return isSectionStart(line) && !isGroupSection(line);
        }

        private string extractGroupsSection()
        {
            return _fileContents.Substring(startOfGroupsSection(), endOfGroupsSection() - startOfGroupsSection());
        }

        private int endOfGroupsSection()
        {
            return _fileContents.IndexOf(SECTION_NAME_PREFIX, startOfGroupsSection() + 1);
        }

        private int startOfGroupsSection()
        {
            return _fileContents.IndexOf(GROUPS_SECTION_TOKEN);
        }

        public Group FindGroupByName(string name)
        {
            return _groups.Find(delegate(Group current) { return current.Name.Equals(name.Trim()); });
        }

        public void CreateGroup(string groupName)
        {
            CreateGroup(groupName, new List<ISecurityPrincipal>());
        }

        public void CreateGroup(string groupName, List<ISecurityPrincipal> members)
        {
            if (FindGroupByName(groupName) != null)
                throw new ApplicationException(string.Format("Group '{0}' already exists!", groupName));
            _groups.Add(new Group(groupName, members));
        }

        public void DeleteGroup(string groupName)
        {
            Group group = FindGroupByName(groupName);
            if (group == null)
                throw new ApplicationException(string.Format("Group '{0}' not found for deletion!", groupName));
            _groups.Remove(group);
        }
    }
}